Generated code - Using the entity collection classes, SelfServicing
Preface
Per entity definition in a project, LLBLGen Pro will generate an entity collection class. This class is used to work on
more than one entity at the same time and it is used to retrieve more than one entity of the same type from the database.
This section describes the different kinds of functionality bundled in the collection classes and how to utilize that functionality in
your code.
Entity collections and generics
In .NET 1.x, the generated collection classes derive from
EntityCollectionBase. In .NET 2.0, the generated collection classes derive from
EntityCollectionBase(Of
EntityType), for example: CustomerCollection derives from EntityCollectionBase<CustomerEntity> (C#) or
EntityCollectionBase(Of CustomerEntity). This has consequences for inheritance. If there are two entities, Employee and Manager, and Manager is a
subtype of Employee, then ManagerCollection doesn't derive from EmployeeCollection, but it derives directly from
EntityCollectionBase<ManagerEntity>.
The reason for this is that the collection classes in SelfServicing aren't a generic type. This is done to
be sure code which is generated with a previous version of LLBLGen Pro is migratable to v2 of LLBLGen Pro without much problems. If ManagerCollection
would inherit from EmployeeCollection, ManagerCollection would still be of type EntityCollectionBase<EmployeeEntity>. It was a design decision to
keep the generated collection classes as non-generic classes, e.g. ManagerCollection instead of EntityCollection<ManagerEntity>, to keep backwards
compatibility for users who migrate from an older version of LLBLGen Pro to v2.
Entity retrieval into an entity collection object
Entity collection objects can be filled with entities retrieved from the database using several ways. Below we'll walk you
through the ones you will use the most.
Using a related entity
The easiest way to retrieve a set of entities in an entity collection class, is by using a related entity, which in turn is used as
a filter. For example, let's use our user "CHOPS" again and let's retrieve all the order entities for that customer:
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS");
OrderCollection orders = customer.Orders;
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS")
Dim orders As OrderCollection = customer.Orders
The entity inside 'customer' is used to filter the orders in the persistent storage and retrieve them as entity instances in the
OrderCollection 'orders'. The collection is filled internally by calling an overloaded version of customer.GetMultiOrders().
That method will do the actual retrieval. You can call GetMultiOrders() yourself directly too:
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS");
OrderCollection orders = customer.GetMultiOrders(false);
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS")
Dim orders As OrderCollection = customer.GetMultiOrders(False)
'false/False' is passed in as parameter, which tells GetMultiOrders to fetch the entities from the persistent storage if the orders for
this customer aren't already fetched, and just return the collection if the orders are already fetched. This is called
lazy loading or
Load on demand, since the collection of orders is loaded when you ask for it, not when the customer is loaded.
This improves performance. When you specify true/True, GetMultiOrders() will always refetch the related order entities. This can be
handy if you want to refresh the set of orders in a customer you hold in memory.
In our example of Customer and an Orders collection,
the CustomerEntity has a property called AlwaysFetchOrders. Default for this property is 'false'. Setting this property to
to true, will thus load the related entity each time you access the property. This can be handy if you want to stay up to date with the related entity state in
the database. It can degrade performance, so use the property with care. Forcing a fetch has a difference with AlwaysFetch
FieldMappedOnRelation in that
a forced fetch will clear the collection first, while AlwaysFetch
FieldMappedOnRelation does not. A forced fetch will thus remove new entities
added to the collection from that collection as these are not yet stored in the database.
GetMultiOrders() has more overloads than this one. You can, for example, specify an extra filter using a predicate expression, which
lets you filter even further on the orders, thus for example all orders before a given date. That will result in all orders for that
particular customer created before a given date. In some overloads it also accepts an entity factory object. You can use that to
create special Order entity classes using the GetMultiOrders() routine: the entity factory object will be used by the data retriever
to create a new entity for every read datarow, in this case order data. You probably will not use this option unless you want to
extend the framework dramatically.
To apply sorting and a maximum number of items to return for every time you do customer.Orders for that particular customer object,
you can set the sortclauses and the maximum number of entities to return by calling customer.SetCollectionParametersOrders():
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS");
ISortExpression sorter = new SortExpression(OrderFields.OrderDate | SortOperator.Descending);
customer.SetCollectionParametersOrders(10, sorter);
OrderCollection orders = customer.GetMultiOrders(false);
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS")
Dim sorter As ISortExpression = New SortExpression( _
New SortClause(OrderFields.OrderDate, SortOperator.Descending))
customer.SetCollectionParametersOrders(10, sorter)
Dim orders As OrderCollection = customer.GetMultiOrders(False)
Now a call to GetMultiOrders or customer.Orders (which is used in databinding scenarios also) will sort the orders on OrderDate,
descending and will return a maximum of 10 order entities.
If Order is in an inheritance hierarchy, the fetch is polymorphic. This means that if the customer entity, in this case customer "CHOPS", has references to
instances of different derived types of Order, every instance in customer.Orders is of the type it represents, which effectively means that
not every instance in Orders is of the same type. See for more information about polymorphic fetchs also
Polymorphic fetches.
Using a prefetch path
An easy way to retrieve a set of entities can be by using a Prefetch Path, to read related entities together with the entity or entities to fetch. See
for more information about Prefetch Paths and how to use them:
Prefetch Paths.
Using the collection object
The most flexible way to retrieve a set of entities in an entity collection is by simply using the entity collection object and its
many methods to retrieve entities of the type related to that particular entity collection. Let's concentrate on the OrderCollection.
The entity order has a rich set of different relationships, with Customer, Employee and Shipper (m:1 relations), with OrderDetails
(1:n relation) and with Product (m:n relation over OrderDetail)
LLBLGen Pro will add retrieval methods to the entity collection object to utilize these relationships (except 1:1 and
1:n relationships which always will result in a single instance) to retrieve a set of entities fast. Lets look at the two relation types
which will end up in methods in the collection classes: m:1 and m:n.
Using the m:1 relations
First the m:1 relationships. All entities with an m:1 relationship with the current entity, in this case Order, will be added as
filter entities to a single method which has several overloads: GetMultiManyToOne(). This way you can filter on orders by specifying
one or more (or all) of the related entities as filters. In the case of the Order entity, the GetMultiManyToOne() overloads accept
all those three entities, and if one is specified, the values of that entity's field(s) which are directly related to the Order entity
(for customer this is for example the CustomerID) are used as filters, defining which orders to load into the collection. As an example, we'll
load all orders of the customer "CHOPS" which were placed by the employee with employeeID 1.
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS");
EmployeeEntity employee = new EmployeeEntity(1);
OrderCollection orders = new OrderCollection();
orders.GetMultiManyToOne(customer, employee, null);
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS")
Dim employee As New EmployeeEntity(1)
Dim orders As New OrderCollection()
orders.GetMultiManyToOne(customer, employee, Nothing)
We pass in two existing entity objects, which both have their entity data loaded into memory, as filters to the GetMultiManyToOne()
routine and we pass 'null/Nothing' for the Shipper entity, which will tell the dynamic query engine (DQE) to construct a retrieval
query with a filter on just customer and employee. The power of the relations is used to construct filters which
do not require you to construct relation collections or predicate expressions.
If you want to limit the number of objects returned in the collection, want to sort the objects before they're added to the
collection or want to filter the objects further (for example in this case: all orders before a given date), you can specify
a number for the maximum number of objects to return (0 for all objects), a set of sortclauses and an extra predicate expression,
using one of the overloaded GetMultiManyToOne methods. See for details about sortclauses and predicate expressions
Getting started with filtering and
Sorting.
Using the m:n relations
When an object has one or more m:n (many to many) relationships with other entities, LLBLGen Pro will also generate easy to use
filter methods to filter objects, using the related entity. Per entity related with an m:n relation there is one GetMultiManyToManyUsing
Field mapped on m:n relation
which accepts an instance of the related entity for a filter. Each GetMultiManyToMany* routine has an overload which accepts a
maximum delimiter for limiting the number of objects returned, and it accepts a set of sortclauses as well, to sort the objects before they're
added to the collection. Let's retrieve all orders which contain the purchase of a product X with productID 10. In the Order entity,
we named the field mapped on the m:n relation Order - Product 'Products', which thus ends up in the method name:
// [C#]
ProductEntity product = new ProductEntity(10);
OrderCollection orders = new OrderCollection();
orders.GetMultiManyToManyUsingProducts(product);
' [VB.NET]
Dim product As New ProductEntity(10)
Dim orders As New OrderCollection()
orders.GetMultiManyToManyUsingProducts(product)
We also could have used the product entity itself and retrieve the same set of orders:
// [C#]
ProductEntity product = new ProductEntity(10);
OrderCollection orders = product.Orders;
' [VB.NET]
Dim product As New ProductEntity(10)
Dim orders As OrderCollection = product.Orders
or even:
// [C#]
ProductEntity product = new ProductEntity(10);
OrderCollection orders = product.GetMultiOrders(false);
' [VB.NET]
Dim product As New ProductEntity(10)
Dim orders As OrderCollection = product.GetMultiOrders(False)
There are multiple ways to retrieve the same data in the framework LLBLGen Pro generates for you. It's up to you which one you'll
use in which situation.
Total control: GetMulti()
LLBLGen Pro also generates a method called GetMulti() with several overloads, which allows you complete freedom of how you want
to retrieve the entities: it accepts a predicate expression as filter, sortclauses for sorting, a value for the maximum number of
entities to retrieve and if you use a multi-entity filter, a relations collection. GetMulti() has various overloads to ease the
way to call the method. See for an example the Multi-entity filters section in
Advanced filtering or the
small example below, which simply passes null/Nothing as the predicate to GetMulti(), making GetMulti() return all entities, thus no
filtering will be performed:
// [C#]
OrderCollection orders = new OrderCollection();
orders.GetMulti(null); // all orders will be read into the collection
' [VB.NET]
Dim orders As New OrderCollection()
orders.GetMulti(Nothing) ' all orders will be read into the collection
Entity data manipulation using collection classes
Manipulating the entity data of more than one entity at once can be cumbersome when you work with objects that have to be
loaded into memory: all entities you want to manipulate have to be instantiated into memory, you have to alter the fields of
these objects and then save them individually. LLBLGen Pro offers functionality to work on entity data directly in the persistent
storage. This opens up the possibility to do bulk updates or bulk deletes with a single call to a method, greatly reducing the
database traffic and increasing performance. It also improves concurrency safety among threads, because you alter data directly
in the shared repository, so other threads will see changes immediately.
Updating a set of entities in the persistent storage
LLBLGen Pro generates two types of update methods: a generic one, UpdateMulti() and one for the m:1 relations the entity has,
UpdateMultiManyToOne(). Both update methods work practically the same: you specify an entity with the new values for the fields you
want to change (the framework will update the fields which have a changed value) and a filter to narrow down the entities to
update the fields of. The UpdateMulti() takes a predicate expression to filter the entity rows to update, an entity object which
is used to supply the new values and also to determine which fields have to be updated, and if you use a filter spanning multiple
entities, you can supply a relation collection in an overloaded version. The UpdateMultiManyToOne also accepts an entity with the
new values but instead of accepting a predicate expression and relation collection, it accepts a set of related entities. For example,
the Order entity accepts as a filter in its UpdateMultiManyToOne, a customer entity, employee entity and a shipper entity, just
like the GetMultiManyToOne() methods.
As an example, let's update all order rows which have an order detail row with a product with productID 10 and have an OrderID
larger than 11000 by setting the EmployeeID to the value 6. This is a multi-entity filter update, so we have to construct a
predicate expression, a relation collection and
a new order entity with the values to update in the order entity rows filtered out by the specified filter. The UpdateMulti*()
routines will check each field in the passed in entity object. If the field has been changed since the instantiation of the entity object
that field will be updated in all filtered out entities and will be set to the value specified as the field value. In the following
example the code is given to perform such an update.
// [C#]
OrderCollection orders = new OrderCollection();
OrderEntity orderUpdate = new OrderEntity();
orderUpdate.EmployeeID = 6;
RelationCollection relationsToUse = new RelationCollection();
relationsToUse.Add(OrderEntity.Relations.OrderDetailsEntityUsingOrderID);
IPredicateExpression selectFilter = new PredicateExpression(OrderDetailsFields.ProductID==10));
selectFilter.AddWithAnd(OrderFields.OrderID > 11000));
int amountRowsAffected = orders.UpdateMulti(orderUpdate, selectFilter, relationsToUse);
' [VB.NET]
Dim orders As New OrderCollection()
Dim orderUpdate As New OrderEntity()
orderUpdate.EmployeeID = 6
Dim relationsToUse As New RelationCollection()
relationsToUse.Add(OrderEntity.Relations.OrderDetailsEntityUsingOrderID)
Dim selectFilter As IPredicateExpression = New PredicateExpression( _
OrderDetailsFields.ProductID = 10))
selectFilter.AddWithAnd(OrderFields.OrderID > 11000))
Dim amountRowsAffected As Integer = orders.UpdateMulti(orderUpdate, selectFilter, relationsToUse)
Note:
|
In the VB.NET code above, operator overloading is used. If you're using VB.NET on .NET 1.0 or .NET 1.1, you don't have operator overloading
functionality available as VB.NET for .NET 1.x doesn't support operator overloading, it was introduced in VB.NET on .NET 2.0. In the case that you're using .NET 1.x
and VB.NET, create the predicates using the pattern:
New FieldCompareValuePredicate(OrderDetailsFields.ProductID, ComparisonOperator.Equals, 10)
|
You'll notice the return value for UpdateMulti, which is an integer. The value returned will be equal to the number of rows (entities)
affected by the update statement executed. If the RDBMS has been setup to stop row counting, this value will be -1. If no rows were
affected, this value will be 0. The UpdateMultiManyToOne() method works similar but accepts one or more entities for filtering. See the GetMultiManyToOne description
earlier on this page.
Because a single statement is executed, it is automatically run in a transaction. The DQE should take care of a new database
transaction if the database server doesn't support transaction per statement (all major databases do support this, including
SqlServer).
Updating entities in a collection in memory
When you have loaded a set of entities in a collection and for example have bound this collection to a datagrid, the user probably
has altered one or more objects' fields in the collection. You can also alter the fields yourself by looping through the objects
inside the collection. When you want to save these changes to the persistent storage, you can use all save methods of the objects
inside the collection, but you can also use the
SaveMulti() method which walks all objects inside the collection and if the object
is 'dirty' (which means, it's been changed and should be updated in the persistent storage) it is saved. This is all done in a
transaction if no transaction is currently available. (See for more information about transactions the section
Transactions). Entity collections support
saving entities recursively, as discussed in
Saving entities recursively section in Using the entity classes. Just pass true
for the recurse parameter in one of SaveMulti()'s overloads and all entities are saved recursively. This feature is not enabled by default for
backwards compatibility reasons, so if you want to save entities recursively, use the overload of SaveMulti() to be able to specify true for recurse.
Deleting one or more entities from the persistent storage
If you want to delete one or more entities from the persistent storage the same problem as with updating a set of entities
appears: you first have to load them into memory, call Delete() and they'll be deleted. To delete a set of entities from the
persistent storage directly, you can use DeleteMulti() overloads or the DeleteMultiManyToOne method to achieve your goal. All
DeleteMulti*() methods work directly on the persistent storage except one, the DeleteMulti() method which does not take any
parameters. That one works with the objects inside the collection and deletes them one by one from the persistent storage using
an own transaction if the current collection isn't part of an existing transaction. (See for more information about transactions the section
Transactions). The DeleteMulti*() methods which do accept parameters and
thus work on the persistent storage work the same as the UpdateMulti*() methods, except of course the DeleteMulti*() methods do not
accept an entity with changed fields. See for an example how to filter rows for DeleteMulti*() the UpdateMulti() example given earlier
on this page.
Note:
|
DeleteMulti(*) is not supported for entities which are in a hierarchy of type TargetPerEntity
(See Concepts - Entity inheritance and relational models.). This is by design, as
the delete action isn't possible in one go with proper checks due to referential integrity issues.
|
Client side sorting
In v2 of LLBLGen Pro, the entity collections don't implement IBindingList anymore, as the entity collections use
EntityView classes
to bind to grids and other controls and also let these views do the filtering and sorting of the entity collection data. To keep backwards
compatibility, the Sort() methods of the entity collection classes have been kept and work as they did in previous version of LLBLGen Pro.
It's recommended you use an EntityView class to sort and filter an entity collection instead of using the Sort() methods directly on an
entity collection. See for more information about EntityView classes:
Generated code - using entity views with entity collections.
To sort a fetched collection in memory, without going back to the database, use the entity collection method Sort (there are various overloads).
This method uses internally the ArrayList's QuickSort method on the property specified (either by field index or property name). Two
overloads also accept an IComparer object, which will then sort the entities based on the implementation of that IComparer object, which you can
supply yourself. Below is an example how to sort a fetched CustomerCollection in memory, on company name.
// C#
CustomerCollection customers = new CustomerCollection();
customers.GetMulti(null);
customers.Sort((int)CustomerFieldIndex.CompanyName, ListSortDirection.Descending);
' VB.NET
Dim customers As New CustomerCollection()
customers.GetMulti(Nothing)
customers.Sort(CInt(CustomerFieldIndex.CompanyName), ListSortDirection.Descending)
The Sort method can sort on one property/field at a time. Use for an easier way to sort an entity collection an entity view:
Generated code - using entity views with entity collections
Finding entities inside a fetched entity collection
Although it's recommended to use EntityView objects to filter and sort an in-memory entity collection object, it sometimes can be helpful to just
have a quick way to find in an in-memory entity collection an entity or group of entities matching a filter. The entity collection classes offer this facility
through the method
FindMatches(IPredicate). FindMatches is a method which accepts a normal LLBLGen Pro predicate (see for more information about predicates
Generated code - getting started with filtering) and returns a list of indexes of all entities matching that predicate.
As a PredicateExpression is also a predicate, you can specify a complex filter, including filters on non-field properties, to find back the entities you're
looking for. On .NET 1.x, FindMatches returns an ArrayList. On .NET 2.0, FindMatches returns a List<int> / List(Of Integer).
The following example finds all indexes of customer entities from the UK in the fetched entity collection of customers. FindMatches will perform an in-memory
filter, it won't go to the database.
// C#
IPredicate filter = (CustomerFields.Country == "UK");
ArrayList indexes = myCustomers.FindMatches(filter);
' VB.NET
Dim filter As new FieldCompareValuePredicate(CustomerFields.Country, ComparisonOperator.Equal, "UK")
Dim indexes As ArrayList = myCustomers.FindMatches(filter)
Note:
|
If you're using VB.NET and you're using .NET 2.0, you can use the symplified syntaxis using operator overloading:
Dim filter As IPredicate = (CustomerFields.Country = "UK")
|
Note:
|
When using FieldCompareValuePredicate with FindMatches, be sure to specify the value in the same type as teh value of the field. For example, if the field is of type Int64, and you specify as value to compare the value 1, you'll be comparing an Int64 with an Int32, which will fail. Instead, specify the value, 1, as an Int64 as well.
|
FindMatches is the same routine which is also used by EntityViews to find the entities which should belong in the view. As the routine is defined virtual /
Overridable, you can tweak the way the entities are matched.
Hierarchical projections of entity collections
LLBLGen Pro allows you to create projections of the full graph of all the entities inside a given entity collection onto a DataSet or a Dictionary object. A hierarchical projection is a projection where all entities in the entity collection plus all their related entities and so on, are grouped together per entity type. Say you have the following graph in memory: a set of CustomerEntity instances, which contain each a set of OrderEntity instances and each OrderEntity instance refers to an EmployeeEntity instance. This projection functionality is implemented on the entity collection, in the method
CreateHierarchicalProjection. It's implemented on the entity collection classes (e.g. CustomerCollection) and not on the EntityView class because it affects related entities as well, while EntityView is a view of 1-level deep on an entity collection.
With LLBLGen Pro it's possible to project this graph onto a DataSet which will result in per entity type a new DataTable object with all instances of that entity type (and the data relations setup correctly). You can also project it onto a Dictionary (Hashtable in .net 1.x) with per entity type an entity collection which contains the entities of that type. Projections are defined in instances of the
IViewProjectionData interface which is implemented in the
ViewProjectionData class. This class combines per type projections (as shown below in the example) which are then used as one projection on the complete graph.
By default, when projecting to a DataSet, only the entity types which have instances in the graph get a DataTable in the resulting DataSet. If you want to have a DataSet where there are always an expected number of DataTable instances (so for entities which aren't in the graph, they're empty), you can pre-create the DataSet and pass the pre-created DataSet to the projection routine. LLBLGen Pro's runtime library contains helper routines to produce an empty DataSet with empty DataTables, the correct columns and the proper DataRelation objects setup based on a prefetch path specified. Please consult the
LLBLGen Pro Reference Manual for the
GeneralUtils class'
ProduceEmptyDataSet and
ProduceEmptyDataTable routines.
Examples
The following examples will show you both projections (to DataSet and to Dictionary) of the earlier described graph of customers - Orders - Employees. The examples will first fetch the complete graph of customers, orders and employees and will then create a projection of that graph. Usage of custom projections per property and additional filters is also shown by the examples. Please refer to the
LLBLGen Pro reference manual for details about the generic ViewProjectionData class and its constructors. .NET 1.x users should use ArrayList instances instead of List(Of T) and should use the non-generic ViewProjectionData class.
Projection to DataSet
// C#
CustomerCollection customers = new CustomerCollection();
PrefetchPath path = new PrefetchPath(EntityType.CustomerEntity);
path.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathEmployees);
customers.GetMulti(null, path);
// setup projections per type.
List<IEntityPropertyProjector> customerProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.CustomerEntity));
// add an additional projector so the destination DataTable will have an additional column called 'IsNew' with
// the value of the IsNew property of the customer entities.
customerProjections.Add(new EntityPropertyProjector(new EntityProperty("IsNew"), "IsNew"));
List<IEntityPropertyProjector> orderProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.OrderEntity));
List<IEntityPropertyProjector> employeeProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.EmployeeEntity));
List<IViewProjectionData> projectionData = new List<IViewProjectionData>();
// create the customer projection information. Specify a filter so only customers from Germany
// are projected.
projectionData.Add(new ViewProjectionData<CustomerEntity>(
customerProjections, (CustomerFields.Country == "Germany"), true));
projectionData.Add(new ViewProjectionData<OrderEntity>(orderProjections, null, false));
projectionData.Add(new ViewProjectionData<EmployeeEntity>(employeeProjections));
DataSet result = new DataSet("projectionResult");
customers.CreateHierarchicalProjection(projectionData, result);
' VB.NET
Dim customers As New CustomerCollection()
Dim path As New PrefetchPath(EntityType.CustomerEntity)
path.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathEmployees)
customers.GetMulti(Nothing, path)
' setup projections per type.
Dim customerProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.CustomerEntity))
' add an additional projector so the destination DataTable will have an additional column called 'IsNew' with
' the value of the IsNew property of the customer entities.
customerProjections.Add(New EntityPropertyProjector(New EntityProperty("IsNew"), "IsNew"))
Dim orderProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.OrderEntity))
Dim employeeProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.EmployeeEntity))
Dim projectionData As New List(Of IViewProjectionData)()
' create the customer projection information. Specify a filter so only customers from Germany
' are projected.
projectionData.Add(New ViewProjectionData(Of CustomerEntity)( _
customerProjections, (CustomerFields.Country = "Germany"), True))
projectionData.Add(New ViewProjectionData(Of OrderEntity)(orderProjections, Nothing, False))
projectionData.Add(New ViewProjectionData(Of EmployeeEntity)(employeeProjections))
Dim result As New DataSet("projectionResult")
customers.CreateHierarchicalProjection(projectionData, result)
The same projectors as used with the projection to the DataSet are usable with a projection to a Dictionary, which is almost equal to the DataSet example. .NET 1.x users should use a Hashtable object instead of a Dictionary object.
Projection to Dictionary
// C#
CustomerCollection customers = new CustomerCollection();
PrefetchPath path = new PrefetchPath(EntityType.CustomerEntity);
path.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathEmployees);
customers.GetMulti(null, path);
// setup projections per type.
List<IEntityPropertyProjector> customerProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.CustomerEntity));
// add an additional projector so the destination DataTable will have an additional column called 'IsNew' with
// the value of the IsNew property of the customer entities.
customerProjections.Add(new EntityPropertyProjector(new EntityProperty("IsNew"), "IsNew"));
List<IEntityPropertyProjector> orderProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.OrderEntity));
List<IEntityPropertyProjector> employeeProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.EmployeeEntity));
List<IViewProjectionData> projectionData = new List<IViewProjectionData>();
// create the customer projection information. Specify a filter so only customers from Germany
// are projected.
projectionData.Add(new ViewProjectionData<CustomerEntity>(
customerProjections, (CustomerFields.Country == "Germany"), true));
projectionData.Add(new ViewProjectionData<OrderEntity>(orderProjections, null, false));
projectionData.Add(new ViewProjectionData<EmployeeEntity>(employeeProjections));
Dictionary<Type, IEntityCollection> projectionResults = new Dictionary<Type, IEntityCollection>();
customers.CreateHierarchicalProjection(projectionData, projectionResults);
' VB.NET
Dim customers As New CustomerCollection()
Dim path As New PrefetchPath(EntityType.CustomerEntity)
path.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathEmployees)
customers.GetMulti(Nothing, path)
' setup projections per type.
Dim customerProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.CustomerEntity))
' add an additional projector so the destination DataTable will have an additional column called 'IsNew' with
' the value of the IsNew property of the customer entities.
customerProjections.Add(New EntityPropertyProjector(New EntityProperty("IsNew"), "IsNew"))
Dim orderProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.OrderEntity))
Dim employeeProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.EmployeeEntity))
Dim projectionData As New List(Of IViewProjectionData)()
' create the customer projection information. Specify a filter so only customers from Germany
' are projected.
projectionData.Add(New ViewProjectionData(Of CustomerEntity)( _
customerProjections, (CustomerFields.Country = "Germany"), True))
projectionData.Add(New ViewProjectionData(Of OrderEntity)(orderProjections, Nothing, False))
projectionData.Add(New ViewProjectionData(Of EmployeeEntity)(employeeProjections))
Dim projectionResults As New Dictionary(Of Type, IEntityCollection)()
customers.CreateHierarchicalProjection(projectionData, projectionResults)
Note: |
If you just want a structure with per entity type a collection with all the instances of that type in the entity graph, so not really a projection to new copies of the entities, please use the routine ObjectGraphUtils.ProduceCollectionsPerTypeFromGraph. The ObjectGraphUtils class is located in the ORMSupportClasses namespace and contains a variety of routines working on entity graphs. Please see the LLBLGen Pro reference manual for details on this class and this method. |
Tracking entity remove actions
Removing an entity from a collection by calling
entitycollection.
Remove(toRemove) or
entitycollection.
RemoveAt(index) is an ambiguous action: do you want to remove the entity from the collection to further process the entities left, or do you want to get rid of the entity completely, both in-memory and also in the database? This is the reason why LLBLGen Pro doesn't perform deletes on the database automatically if you remove an entity from a collection, you have to explicitly specify what entities to remove.
Tracking which entities are removed from an entity collection to be removed from the database can be a bit cumbersome if the collection is bound to a grid for example. To overcome this, LLBLGen Pro has a feature which makes an entity collection track the entities removed from it by using
another entity collection. This way, you can keep track of which entities are removed from the entity collection and pass them on to a
Unit of work object for persistence in one transaction together with the rest of the entities which have changed.
The extra collection is necessary because an entity which is removed from the collection isn't there anymore, so it can't be referred to by the collection itself. To enable removal tracking in an entity collection, set its
RemovedEntitiesTracker property to a collection into which you want to track the removed entities from the collection. This collection can then be added to a UnitOfWork object for deletion by using the method
unitofwork.
AddCollectionForDelete(collectionWithEntitiesToDelete) or you can delete the entities by calling
entitycollection.
DeleteMulti() on the entity collection with the tracked removed entities.
The following example illustrates this.
// C#
// First fetch all customers from Germany with their orders.
CustomerCollection customers = new CustomerCollection();
PrefetchPath path = new PrefetchPath(EntityType.CustomerEntity);
path.Add(CustomerEntity.PrefetchPathOrders);
customers.GetMulti(CustomerFields.Country == "Germany", path);
// we now will add a tracker collection to the orders collection of customer 0.
OrderCollection tracker = new OrderCollection();
customers[0].Orders.RemovedEntitiesTracker = tracker;
// after this, we can do this:
customers[0].Orders.Remove(myOrder);
// and myOrder is removed from the in-memory collection customers[0].Orders
// and it's placed in tracker. We can now delete the entities in tracker
// by using a UnitOfWork object or by calling tracker.DeleteMulti().
' VB.NET
' First fetch all customers from Germany with their orders.
Dim customers As New CustomerCollection()
Dim path As New PrefetchPath(EntityType.CustomerEntity)
path.Add(CustomerEntity.PrefetchPathOrders)
customers.GetMulti(CustomerFields.Country = "Germany", path)
' we now will add a tracker collection to the orders collection of customer 0.
Dim tracker As New OrderCollection()
customers(0).Orders.RemovedEntitiesTracker = tracker
' after this, we can do this:
customers(0).Orders.Remove(myOrder)
' and myOrder is removed from the in-memory collection customers[0].Orders
' and it's placed in tracker. We can now delete the entities in tracker
' by using a UnitOfWork object or by calling tracker.DeleteMulti().
Note: |
Tracking removal of an entity isn't used by the Clear() method, because Clear is often used to clean up a collection and not to remove entities from the database, so to avoid false positives and the deletion of entities which weren't suppose to be deleted, removal tracking isn't available for the Clear method. |